package threeAddressGenerator;

import gen.GoLexer;
import gen.GoParser;
import gen.GoParserBaseListener;
import org.antlr.v4.runtime.tree.TerminalNode;
import symbolTable.FunctionSymbol;
import symbolTable.Symbol;
import symbolTable.SymbolTable;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

public class ThreeAddressGenerator extends GoParserBaseListener {

    private LinkedHashMap<String, Symbol> symbols;
    private List<Quadruple> quadruples = new LinkedList<>();

    private int tempNum = 0;

    private int tempLabel = 0;

    private Stack<Integer> bstack = new Stack<>();

    private Stack<Integer> estack = new Stack<>();

    public ThreeAddressGenerator(LinkedHashMap<String, Symbol> symbols) {
        this.symbols = symbols;
    }

    /**
     * check whether primaryExpr is the subtype of embeddedType
     * @param primaryExpr   primaryExpr
     * @param embeddedType  embeddedType
     * @return  if primaryExpr is the subtype of embeddedType return true, else return false
     */
    private boolean checkWhetherSubType(GoParser.PrimaryExprContext primaryExpr, GoParser.Type_Context embeddedType) {
        // TODO:
        return true;
    }

    /**
     * Writes the generated intermediate code to the specified file
     * @param filePath  File path
     * @throws IOException  IOException
     */
    public void writeThreeAddressToFile(String filePath) throws IOException {
        try {
            File file = new File(filePath);
            boolean ok = file.createNewFile();
            if (ok) {
                System.out.println("file create successfully!");
            } else {
                System.out.println("file exist!");
            }
            FileWriter writer = new FileWriter(file);       // Open the file
            for (Quadruple quadruple : quadruples) {
                writer.write(quadruple.toString() + "\r\n");      // Write the generated three-address code to a file
            }
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * go through block manually
     * @param block blockContext
     */
    private void parseBlock(GoParser.BlockContext block) {
        if (block.statementList() != null) {
            for (GoParser.StatementContext statementContext : block.statementList().statement()) {
                parseStatement(statementContext);
            }
        }
    }

    /**
     * define parameters and result for a function or method
     * @param paramsCtx parameters
     * @param resultCtx result
     * @param name  function or method name
     */
    private void defineParametersResultsForFunction(GoParser.ParametersContext paramsCtx, GoParser.ResultContext resultCtx, String name) {
        this.quadruples.add(new Quadruple(Quadruple.Op.FUNC, name, null, null));
        List<GoParser.ParameterDeclContext> paramCtxs = paramsCtx.parameterDecl();
        for (GoParser.ParameterDeclContext paramCtx : paramCtxs) {  // declare parameters
            List<TerminalNode> params = paramCtx.IDENTIFIER();
            for (TerminalNode param : params) {
                this.quadruples.add(new Quadruple(Quadruple.Op.PARAM, param.getText(), null, null));
            }
        }
        if (resultCtx != null) {      // if the function or method has result
            if (resultCtx.parameters() != null) {   // result is parameters
                paramCtxs = resultCtx.parameters().parameterDecl();
                for (GoParser.ParameterDeclContext paramCtx : paramCtxs) {
                    List<TerminalNode> params = paramCtx.IDENTIFIER();
                    for (TerminalNode param : params) {
                        this.quadruples.add(new Quadruple(Quadruple.Op.RESULT, param.getText(), SymbolTable.getEmbeddedType_(paramCtx.type_()).getText(), null));
                    }
                }
            }
        }
    }

    /**
     * define vars with a list of expression
     * @param vars  vars to define
     * @param exprsCtx expressionListContext
     */
    private void defineVarsWithExpression(List<TerminalNode> vars, GoParser.ExpressionListContext exprsCtx) {
        List<GoParser.ExpressionContext> exprCtxs = exprsCtx.expression();
        int varIndex = 0;
        for (GoParser.ExpressionContext exprCtx : exprCtxs) {
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, vars.get(varIndex).getText(), null, null));
            StringBuilder var = new StringBuilder(vars.get(varIndex++).getText());
            if (exprCtx.primaryExpr() != null
                    && exprCtx.primaryExpr().typeAssertion() != null) {   // typeAssertion has two return
                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, vars.get(varIndex).getText(), null, null));
                var.append("_").append(vars.get(varIndex++).getText());
            } else if (exprCtx.primaryExpr() != null
                    && exprCtx.primaryExpr().arguments() != null) {
                FunctionSymbol func = (FunctionSymbol) this.symbols.get(exprCtx.primaryExpr().primaryExpr().getText());
                for (int i = 1; i < func.results.size(); i++) {
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, vars.get(varIndex).getText(), null, null));
                    var.append("_").append(vars.get(varIndex++).getText());
                }
            }
            parseExpression(exprCtx, var.toString());
        }
    }

    /**
     * parse declaration rule
     * @param ctx   declarationContext
     */
    private void parseDeclaration(GoParser.DeclarationContext ctx) {
        if (ctx.constDecl() != null) {  // match rule 1
            for (GoParser.ConstSpecContext constSpecContext : ctx.constDecl().constSpec()) {
                parseConstSpec(constSpecContext);
            }
        } else if (ctx.varDecl() != null) { // match rule 2
            for (GoParser.VarSpecContext varSpecContext : ctx.varDecl().varSpec()) {
                parseVarSpec(varSpecContext);
            }
        } else {    // match rule 3
            for (GoParser.TypeSpecContext typeSpecContext : ctx.typeDecl().typeSpec()) {
                parseTypeSpec(typeSpecContext);
            }
        }
    }

    /**
     * parse varSpec rule
     * @param ctx   varSpecContext
     */
    private void parseVarSpec(GoParser.VarSpecContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();
        if (ctx.expressionList() != null) { // declare a var by expression
            defineVarsWithExpression(vars, ctx.expressionList());
        } else {    // only declare var by type, not initialize
            for (TerminalNode var : vars) {
                if (SymbolTable.getEmbeddedType_(ctx.type_()).typeLit() != null
                && SymbolTable.getEmbeddedType_(ctx.type_()).typeLit().functionType() != null) {    // function type var
                    defineParametersResultsForFunction(SymbolTable.getEmbeddedType_(ctx.type_()).typeLit().functionType().signature().parameters(), SymbolTable.getEmbeddedType_(ctx.type_()).typeLit().functionType().signature().result(), var.getText());
                    this.quadruples.add(new Quadruple(Quadruple.Op.EXIT, var.getText(), null, null));
                } else {
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, var.getText(), null, null));
                }
            }
        }
    }

    /**
     * parse typeSpec rule
     * @param ctx   typeSpecContext
     */
    private void parseTypeSpec(GoParser.TypeSpecContext ctx) {
        this.quadruples.add(new Quadruple(Quadruple.Op.TYPE, ctx.IDENTIFIER().getText(), SymbolTable.getEmbeddedType_(ctx.type_()).getText(), null));
    }

    /**
     * parse constSpec rule
     * @param ctx   constSpecContext
     */
    private void parseConstSpec(GoParser.ConstSpecContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();
        if (ctx.expressionList() != null) { // declare const by expression
            defineVarsWithExpression(vars, ctx.expressionList());
        } else {    // only declare const by type, not initialize
            for (TerminalNode var : vars) {
                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, var.getText(), null, null));
            }
        }
    }

    /**
     * parse expression rule
     * @param ctx   expressionContext
     * @param des   result of quadruple
     */
    private void parseExpression(GoParser.ExpressionContext ctx, String des) {
        if (ctx.primaryExpr() != null) {    // match rule 1
            parsePrimaryExpr(ctx.primaryExpr(), des);
        } else if (ctx.unary_op != null) {  // match rule 2
            String tmpName = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName, null, null));
            parseExpression(ctx.expression(0), tmpName);
            switch (ctx.unary_op.getType()) {
                case GoLexer.PLUS : this.quadruples.add(new Quadruple(Quadruple.Op.ADD, tmpName, null, des)); break;
                case GoLexer.MINUS : this.quadruples.add(new Quadruple(Quadruple.Op.MINUS, tmpName, null, des)); break;
                case GoLexer.EXCLAMATION : this.quadruples.add(new Quadruple(Quadruple.Op.NOT, tmpName, null, des)); break;
                case GoLexer.STAR : this.quadruples.add(new Quadruple(Quadruple.Op.LOAD, tmpName, null, des)); break;//todo
                case GoLexer.AMPERSAND : this.quadruples.add(new Quadruple(Quadruple.Op.ADDRESS, tmpName, null, des)); break;
                case GoLexer.RECEIVE :
                default : break;
            }
        } else if (ctx.mul_op != null) {    // match rule 3
            String tmpName1 = "t" + this.tempNum++, tmpName2 = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName1, null, null));
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName2, null, null));
            parseExpression(ctx.expression(0), tmpName1);
            parseExpression(ctx.expression(1), tmpName2);
            switch (ctx.mul_op.getType()) {
                case GoLexer.STAR : this.quadruples.add(new Quadruple(Quadruple.Op.MUL, tmpName1, tmpName2, des)); break;
                case GoLexer.DIV : this.quadruples.add(new Quadruple(Quadruple.Op.DIV, tmpName1, tmpName2, des)); break;
                case GoLexer.MOD : this.quadruples.add(new Quadruple(Quadruple.Op.MOD, tmpName1, tmpName2, des)); break;
                case GoLexer.LSHIFT : this.quadruples.add(new Quadruple(Quadruple.Op.LSHIFT, tmpName1, tmpName2, des)); break;
                case GoLexer.RSHIFT : this.quadruples.add(new Quadruple(Quadruple.Op.RSHIFT, tmpName1, tmpName2, des)); break;
                case GoLexer.AMPERSAND : this.quadruples.add(new Quadruple(Quadruple.Op.AND, tmpName1, tmpName2, des)); break;
                case GoLexer.BIT_CLEAR: this.quadruples.add(new Quadruple(Quadruple.Op.AND_XOR, tmpName1, tmpName2, des)); break;
                default : break;
            }
        } else if (ctx.add_op != null) {
            String tmpName1 = "t" + this.tempNum++, tmpName2 = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName1, null, null));
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName2, null, null));
            parseExpression(ctx.expression(0), tmpName1);
            parseExpression(ctx.expression(1), tmpName2);
            switch (ctx.add_op.getType()) { // match rule 4
                case GoLexer.PLUS : this.quadruples.add(new Quadruple(Quadruple.Op.ADD, tmpName1, tmpName2, des)); break;
                case GoLexer.MINUS : this.quadruples.add(new Quadruple(Quadruple.Op.MINUS, tmpName1, tmpName2, des)); break;
                case GoLexer.OR : this.quadruples.add(new Quadruple(Quadruple.Op.OR, tmpName1, tmpName2, des)); break;
                case GoLexer.CARET : this.quadruples.add(new Quadruple(Quadruple.Op.XOR, tmpName1, tmpName2, des)); break;
                default : break;
            }
        } else if (ctx.rel_op != null) {    // match rule 5
            String tmpName1 = "t" + this.tempNum++, tmpName2 = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName1, null, null));
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName2, null, null));
            parseExpression(ctx.expression(0), tmpName1);
            parseExpression(ctx.expression(1), tmpName2);
            switch (ctx.rel_op.getType()) {
                case GoLexer.EQUALS : this.quadruples.add(new Quadruple(Quadruple.Op.EQUALS, tmpName1, tmpName2, des)); break;
                case GoLexer.NOT_EQUALS : this.quadruples.add(new Quadruple(Quadruple.Op.NOT_EQUALS, tmpName1, tmpName2, des)); break;
                case GoLexer.LESS : this.quadruples.add(new Quadruple(Quadruple.Op.LESS, tmpName1, tmpName2, des)); break;
                case GoLexer.LESS_OR_EQUALS : this.quadruples.add(new Quadruple(Quadruple.Op.LESS_EQUALS, tmpName1, tmpName2, des)); break;
                case GoLexer.GREATER : this.quadruples.add(new Quadruple(Quadruple.Op.GREATER, tmpName1, tmpName2, des)); break;
                case GoLexer.GREATER_OR_EQUALS :  this.quadruples.add(new Quadruple(Quadruple.Op.GREATER_EQUALS, tmpName1, tmpName2, des)); break;
                default : break;
            }
        } else if (ctx.LOGICAL_AND() != null) { // match rule 6
            String tmpName1 = "t" + this.tempNum++, tmpName2 = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName1, null, null));
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName2, null, null));
            parseExpression(ctx.expression(0), tmpName1);
            parseExpression(ctx.expression(1), tmpName2);
            this.quadruples.add(new Quadruple(Quadruple.Op.LOGICAL_AND, tmpName1, tmpName2, des));
        } else {    // match rule 7
            String tmpName1 = "t" + this.tempNum++, tmpName2 = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName1, null, null));
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, tmpName2, null, null));
            parseExpression(ctx.expression(0), tmpName1);
            parseExpression(ctx.expression(1), tmpName2);
            this.quadruples.add(new Quadruple(Quadruple.Op.LOGICAL_OR, tmpName1, tmpName2, des));
        }
    }

    /**
     * parse primaryExpr rule
     * @param ctx   primaryExprContext
     * @param des   result of quadruple
     */
    private void parsePrimaryExpr(GoParser.PrimaryExprContext ctx, String des) {
        if (ctx.operand() != null) {    // match rule 1
            parseOperand(ctx.operand(), des);
        } else if (ctx.methodExpr() != null) {  // match rule 3
            this.quadruples.add(new Quadruple(Quadruple.Op.LOAD, SymbolTable.getEmbeddedNonNamedType(ctx.methodExpr().nonNamedType()).getText(), ctx.methodExpr().IDENTIFIER().getText(), des));
        } else {
            if (ctx.IDENTIFIER() != null) {     // match rule 4.1
                // 如果成员变量或方法只有一层嵌套则不进行ctx.primaryExpr的解析
                String structName = null;
                if (ctx.primaryExpr().operand() != null) {
                    structName = ctx.primaryExpr().operand().getText();
                } else {
                    // 反之解析ctx.primaryExpr
                    structName = "t" + this.tempNum++;
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, structName, null, null));
                    parsePrimaryExpr(ctx.primaryExpr(), structName);
                }
                this.quadruples.add(new Quadruple(Quadruple.Op.LOAD, structName, ctx.IDENTIFIER().getText(), des));
            } else if (ctx.index() != null) {   // match rule 4.2
                // 如果数组以及索引只有一层嵌套则不进行ctx.primaryExpr和ctx.expression的解析
                String arrayName, indexName;
                if (ctx.primaryExpr().operand() != null) {
                    arrayName = ctx.primaryExpr().operand().getText();
                } else {
                    arrayName = "t" + this.tempNum++;
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, arrayName, null, null));
                    parsePrimaryExpr(ctx.primaryExpr(), arrayName);
                }

                if (ctx.index().expression().primaryExpr() != null
                && ctx.index().expression().primaryExpr().operand() != null) {
                    indexName = ctx.index().expression().primaryExpr().operand().getText();
                } else {
                    indexName = "t" + this.tempNum++;
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, indexName, null, null));
                    parseExpression(ctx.index().expression(), indexName);
                }
                this.quadruples.add(new Quadruple(Quadruple.Op.BIAS, indexName, arrayName, des));
            } else if (ctx.slice_() != null) {  // match rule 4.3
                //TODO:
            } else if (ctx.typeAssertion() != null) {   // match rule 4.4
//                String[] results = des.split("_", 2);
//                int tmp = this.tempNum++;
//                parsePrimaryExpr(ctx.primaryExpr(), "t" + tmp);
//                this.quadruples.add(new Quadruple(Quadruple.Op.ASSIGN, "t" + tmp, SymbolTable.getEmbeddedType_(ctx.typeAssertion().type_()).getText(), results[0]));
//                boolean success = checkWhetherSubType(ctx.primaryExpr(), SymbolTable.getEmbeddedType_(ctx.typeAssertion().type_()));
//                this.quadruples.add(new Quadruple(Quadruple.Op.ASSIGN, String.valueOf(success), null, results[1]));
            } else {    // match rule 4.5
                if ("fmt.Printf".equals(ctx.primaryExpr().getText())) {
                    List<GoParser.ExpressionContext> exprs = ctx.arguments().expressionList().expression();
                    Quadruple quadruple = new Quadruple();
                    if (exprs.size() >= 3) {
                        quadruple.setArg_2(exprs.get(2).getText());
                    }
                    quadruple.setArg_1(exprs.get(1).getText());
                    quadruple.setResult(exprs.get(0).getText());
                    quadruple.setOp(Quadruple.Op.PRINT);
                    this.quadruples.add(quadruple);
                } else if ("fmt.Scanf".equals(ctx.primaryExpr().getText())) {
                    List<GoParser.ExpressionContext> exprs = ctx.arguments().expressionList().expression();
                    Quadruple quadruple = new Quadruple();
                    if (exprs.size() >= 3) {
                        quadruple.setArg_2(exprs.get(2).getText());
                    }
                    quadruple.setArg_1(exprs.get(1).getText());
                    quadruple.setResult(exprs.get(0).getText());
                    quadruple.setOp(Quadruple.Op.SCAN);
                    this.quadruples.add(quadruple);
                } else {
                    String funcName;
                    // 如果是函数调用则ctx.primaryExpr.operand!=null且为函数名
                    if (ctx.primaryExpr().operand() != null
                    && ctx.primaryExpr().operand().operandName() != null) {
                        funcName = ctx.primaryExpr().operand().operandName().IDENTIFIER().getText();
                    } else {
                        // 方法调用则解析方法之后再放入CALL指令
                        funcName = "t" + this.tempNum++;
                        this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, funcName, null, null));
                        parsePrimaryExpr(ctx.primaryExpr(), funcName);
                    }
                    this.quadruples.add(new Quadruple(Quadruple.Op.CALL, funcName, null, null));
                    if (ctx.arguments().expressionList() != null) { // declare arguments
                        for (GoParser.ExpressionContext expressionContext : ctx.arguments().expressionList().expression()) {
                            // 如果参数是简单操作数则直接使用ARGUMENT指令
                            String argName = null;
                            if (expressionContext.primaryExpr().operand() != null) {
                                argName = expressionContext.primaryExpr().operand().getText();
                            } else {
                                argName = "t" + this.tempNum++;
                                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, argName, null, null));
                                parseExpression(expressionContext, argName);
                            }
                            this.quadruples.add(new Quadruple(Quadruple.Op.ARGUMENT, argName, null, null));
                        }
                    }
                    if (ctx.arguments().nonNamedType() != null) {
                        this.quadruples.add(new Quadruple(Quadruple.Op.ARGUMENT, SymbolTable.getEmbeddedNonNamedType(ctx.arguments().nonNamedType()).getText(), null, null));
                    }

                    String[] returns = des.split("_");
                    for (String ret : returns) {
                        this.quadruples.add(new Quadruple(Quadruple.Op.RETURN, ret, null, null));
                    }
                }
            }
        }
    }

    /**
     * parse operand rule
     * @param ctx   operandContext
     * @param des   result of quadruple
     */
    private void parseOperand(GoParser.OperandContext ctx, String des) {
        if (ctx.literal() != null) {
            if (ctx.literal().basicLit() != null) {     // match rule 1.1
                this.quadruples.add(new Quadruple(Quadruple.Op.ASSIGN, ctx.literal().basicLit().getText(), null, des));
            } else if (ctx.literal().compositeLit() != null) {  // match rule 1.2
                parseCompositeLit(ctx.literal().compositeLit(), des);
            } else {    // match rule 1.3
                defineParametersResultsForFunction(ctx.literal().functionLit().signature().parameters(), ctx.literal().functionLit().signature().result(), des);
                parseBlock(ctx.literal().functionLit().block());
                this.quadruples.add(new Quadruple(Quadruple.Op.EXIT, des, null, null));
            }
        } else if (ctx.operandName() != null) { // match rule 2
            this.quadruples.add(new Quadruple(Quadruple.Op.ASSIGN, ctx.operandName().getText(), null, des));
        } else {    // match rule 3
            parseExpression(ctx.expression(), des);
        }
    }

    /**
     * parse compositeLit rule
     * @param ctx   compositeLitContext
     * @param des   result of quadruple
     */
    private void parseCompositeLit(GoParser.CompositeLitContext ctx, String des) {
        // TODO:
    }

    private void parseStatement(GoParser.StatementContext ctx) {
        if (ctx.declaration() != null) {    // match rule 1
            parseDeclaration(ctx.declaration());
        } else if (ctx.labeledStmt() != null) { // match rule 2
            parseLabeledStmt(ctx.labeledStmt());
        } else if (ctx.simpleStmt() != null) {  // match rule 3
            parseSimpleStmt(ctx.simpleStmt());
        } else if (ctx.goStmt() != null) {  // match rule 4
            parseGoStmt(ctx.goStmt());
        } else if (ctx.ifStmt() != null) {  // match rule 11
            parseIfStmt(ctx.ifStmt());
        } else if (ctx.returnStmt() != null) {  // match rule 5
//            parseReturnStmt(ctx.returnStmt());
        } else if (ctx.gotoStmt() != null ){    // match rule 8
            parseGotoStmt(ctx.gotoStmt());
        } else if (ctx.forStmt() != null ){    // match rule 8
            parseForStmt(ctx.forStmt());
        } else if (ctx.breakStmt() != null ){
            parseBreakStmt(ctx.breakStmt());
        } else if (ctx.continueStmt()!= null){
            parseContinueStmt(ctx.continueStmt());
        }
    }

    private void parseGoStmt(GoParser.GoStmtContext ctx) {
        int tmp1 = this.tempNum++, tmp2 = this.tempNum++;
        parseExpression(ctx.expression(), "t" + tmp1);
        this.quadruples.add(new Quadruple(Quadruple.Op.ADDRESS, "t" + tmp1, null, "t" + tmp2));
        this.quadruples.add(new Quadruple(Quadruple.Op.GO, "t" + tmp2, null, null));
    }

    private void parseSimpleStmt(GoParser.SimpleStmtContext ctx) {
        if (ctx.sendStmt() != null) {   // match rule 1
            parseSendStmt(ctx.sendStmt());
        } else if (ctx.incDecStmt() != null) {  // match rule 2
            parseIncDecStmt(ctx.incDecStmt());
        } else if (ctx.assignment() != null) {  // match rule 3
            parseAssignment(ctx.assignment());
        } else if (ctx.expressionStmt() != null) {  // match rule 4
            parseExpression(ctx.expressionStmt().expression(), null);
        } else {    // match rule 5
            parseShortVarDecl(ctx.shortVarDecl());
        }
    }

    private void parseShortVarDecl(GoParser.ShortVarDeclContext ctx) {
        List<TerminalNode> vars = ctx.IDENTIFIER();
        int indexOfVar = 0;
        for (GoParser.ExpressionContext expr : ctx.expressionList().expression()) {
            if (expr.primaryExpr() != null
            && expr.primaryExpr().typeAssertion() != null) {
                String var = vars.get(indexOfVar++).getText() + "_" + vars.get(indexOfVar++).getText();
                parseExpression(expr, var);
            } else {
                parseExpression(expr, vars.get(indexOfVar++).getText());
            }
        }
    }

    private void parseAssignment(GoParser.AssignmentContext ctx) {
        ArrayList<String> left = new ArrayList<>();
        ArrayList<String> right = new ArrayList<>();
        for (GoParser.ExpressionContext expressionContext : ctx.expressionList(0).expression()) {   // parse left expressions
            String exprName;
            if (expressionContext.primaryExpr() != null
            && expressionContext.primaryExpr().operand() != null) {
                exprName = expressionContext.getText();
            } else {
                exprName = "t" + this.tempNum++;
                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, exprName, null, null));
                parseExpression(expressionContext, exprName);
            }

            left.add(exprName);
        }
        for (GoParser.ExpressionContext expressionContext : ctx.expressionList(1).expression()) {   // parse right expression
            if (expressionContext.primaryExpr() != null
            && expressionContext.primaryExpr().arguments() != null) {   // typeAssertion has two result
                FunctionSymbol func = (FunctionSymbol) this.symbols.get(expressionContext.primaryExpr().primaryExpr().getText());
                String result = "t" ;
                StringBuilder des = new StringBuilder(result);
                right.add(result);
                for (int i = 1; i < func.results.size(); i++) {
                    result = "t" + this.tempNum++;
                    this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, result, null, null));
                    des.append("_").append(result);
                    right.add(result);
                }
                parseExpression(expressionContext, des.toString());
            } else {
                String result = "t" + this.tempNum++;
                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, result, null, null));
                parseExpression(expressionContext, result);
                right.add(result);
            }
        }
        assert left.size() == right.size();

        for (int i = 0; i < left.size(); ++i) {
            switch (ctx.assign_op.getType()) {
                case GoLexer.PLUS_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.ADD, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.MINUS_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.MINUS, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.OR_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.OR, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.CARET_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.XOR, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.STAR_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.MUL, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.DIV_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.DIV, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.MOD_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.MOD, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.LSHIFT_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.LSHIFT, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.RSHIFT_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.RSHIFT, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.AMPERSAND_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.AND, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.BIT_CLEAR_ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.AND_XOR, left.get(i), right.get(i), left.get(i))); break;
                case GoLexer.ASSIGN : this.quadruples.add(new Quadruple(Quadruple.Op.ASSIGN, right.get(i), null, left.get(i))); break;
                default : break;
            }
        }
    }

    private void parseIncDecStmt(GoParser.IncDecStmtContext ctx) {
        String operandName;
        if (ctx.expression().primaryExpr() != null
        && ctx.expression().primaryExpr().operand() != null
        && ctx.expression().primaryExpr().operand().operandName() != null) {
            operandName = ctx.expression().primaryExpr().getText();
        } else {
            operandName = "t" + this.tempNum++;
            this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, operandName, null, null));
            parseExpression(ctx.expression(), operandName);
        }
        if (ctx.PLUS_PLUS() != null) {
            this.quadruples.add(new Quadruple(Quadruple.Op.ADD, operandName, "1", operandName));
        } else {
            this.quadruples.add(new Quadruple(Quadruple.Op.MINUS, operandName, "1", operandName));
        }
    }

    private void parseSendStmt(GoParser.SendStmtContext ctx) {

    }

    private void parseLabeledStmt(GoParser.LabeledStmtContext ctx) {
        String labelName = ctx.IDENTIFIER().getText();
        this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null, labelName));
        parseStatement(ctx.statement());
    }

    private void parseReturnStmt(GoParser.ReturnStmtContext ctx) {
        if( ctx.expressionList()==null){
            this.quadruples.add(new Quadruple(Quadruple.Op.RETURN, null, null, null));
        } else {
            int current = tempNum++;
            for (GoParser.ExpressionContext expr : ctx.expressionList().expression()) {
                int tmp = this.tempNum++;
                parseExpression(expr, "t" + tmp);
            }
            this.quadruples.add(new Quadruple(Quadruple.Op.RETURN, null, null, "t" + current));
        }
    }

    private void parseBreakStmt(GoParser.BreakStmtContext ctx) {
        int index = estack.peek();
        this.quadruples.add(new Quadruple(Quadruple.Op.GOTO, null, null, "label" + index));
    }

    private void parseContinueStmt(GoParser.ContinueStmtContext ctx) {
        //TODO:
    }

    private void parseGotoStmt(GoParser.GotoStmtContext ctx) {
        this.quadruples.add(new Quadruple(Quadruple.Op.GOTO, null, null, ctx.IDENTIFIER().getText()));
    }

    private void parseIfStmt(GoParser.IfStmtContext ctx) {
        int tmp1 = this.tempNum++, tmp2 = this.tempLabel++, tmp3 = this.tempLabel++;
        parseExpression(ctx.expression(),"t" + tmp1);
        this.quadruples.add(new Quadruple(Quadruple.Op.FJUMP, "t" + tmp1, null, "label" + tmp2));
        if(ctx.ifStmt()!=null){
             parseIfStmt(ctx.ifStmt());
        }
        if(ctx.block(1)!=null){
            parseBlock(ctx.block(1));
            this.quadruples.add(new Quadruple(Quadruple.Op.GOTO, null, null, "label" + tmp3));
            this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null , "label" + tmp2));
            parseBlock(ctx.block(0));
            this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null , "label" + tmp3));
        }else{
            this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null , "label" + tmp3));
            parseBlock(ctx.block(0));
        }
    }

    private void parseForStmt(GoParser.ForStmtContext ctx){
        if(ctx.forClause().simpleStmt(0) != null){      // A simple statement executed before the loop
            parseSimpleStmt(ctx.forClause().simpleStmt(0));
        }
        //parseExpression(ctx.forClause(), "t" + this.tempNum++);
        int lfOrSta = this.tempLabel++;
        int lfOrEnd = this.tempLabel++;
        bstack.push(lfOrSta);
        estack.push(lfOrEnd);
        this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null , "label" + lfOrSta));
        parseBlock(ctx.block());          // The block executed in the loop
        if(ctx.forClause().simpleStmt(1)!=null){
            parseSimpleStmt(ctx.forClause().simpleStmt(1));
        }
        if(ctx.forClause().expression()!=null){
            int tmp = this.tempNum++;
            parseExpression(ctx.forClause().expression(), "t" + tmp);
            this.quadruples.add(new Quadruple(Quadruple.Op.FJUMP, "t" + tmp, null , "label" + lfOrSta));
        }
        this.quadruples.add(new Quadruple(Quadruple.Op.LABEL, null, null , "label" + lfOrEnd));
        bstack.pop();
        estack.pop();
    }

    @Override
    public void enterFunctionDecl(GoParser.FunctionDeclContext ctx) {
        defineParametersResultsForFunction(ctx.signature().parameters(), ctx.signature().result(), ctx.IDENTIFIER().getText());
        parseBlock(ctx.block());
    }

    @Override
    public void exitFunctionDecl(GoParser.FunctionDeclContext ctx) {
        this.quadruples.add(new Quadruple(Quadruple.Op.EXIT, ctx.IDENTIFIER().getText(), null, null));
    }

    @Override
    public void enterMethodDecl(GoParser.MethodDeclContext ctx) {
        List<GoParser.ParameterDeclContext> paramCtxs = ctx.receiver().parameters().parameterDecl();
        for (GoParser.ParameterDeclContext paramCtx : paramCtxs) {
            List<TerminalNode> params = paramCtx.IDENTIFIER();
            for (TerminalNode param : params) {
                this.quadruples.add(new Quadruple(Quadruple.Op.ALLOC, param.getText(), null, null));
            }
        }
        defineParametersResultsForFunction(ctx.signature().parameters(), ctx.signature().result(), ctx.IDENTIFIER().getText());
        parseBlock(ctx.block());
    }

    @Override
    public void exitMethodDecl(GoParser.MethodDeclContext ctx) {
        this.quadruples.add(new Quadruple(Quadruple.Op.EXIT, ctx.IDENTIFIER().getText(), null, null));
    }

    @Override
    public void enterDeclaration(GoParser.DeclarationContext ctx) {
        if (!(ctx.getParent() instanceof GoParser.StatementContext))
            parseDeclaration(ctx);
    }
}